package org.budo.support.lang.util;

import org.budo.support.assertion.Assert;
import org.budo.support.java.net.util.JavaNetUrlUtil;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author limingwei
 * @date 2016年6月7日 上午9:53:24
 */
public class IoUtil {
    private static final Logger log = Slf4j.getLogger();

    public static String bufferedReadLineToString(Reader reader) {
        List<String> lines = bufferedReadLineToStringList(reader);
        return StringUtil.join(lines, "\r\n");
    }

    public static String bufferedReadLineToString(File file) {
        Reader fileReader = fileReader(file);
        return bufferedReadLineToString(fileReader);
    }

    public static void close(Closeable closeable) {
        if (null == closeable) {
            return;
        }

        try {
            closeable.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void close(InputStream inputStream) {
        if (null == inputStream) {
            return;
        }

        try {
            inputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void closeQuietly(OutputStream outputStream) {
        if (null == outputStream) {
            return;
        }

        try {
            outputStream.close();
        } catch (Throwable e) {
            log.error("#74 closeQuietly error, e=" + e);
        }
    }

    public static void closeQuietly(InputStream inputStream) {
        if (null == inputStream) {
            return;
        }

        try {
            inputStream.close();
        } catch (Throwable e) {
            log.error("#74 closeQuietly error, e=" + e);
        }
    }

    public static void closeQuietly(Reader reader) {
        if (null == reader) {
            return;
        }

        try {
            reader.close();
        } catch (Throwable e) {
            log.error("#84 closeQuietly error, e=" + e);
        }
    }

    public static void close(OutputStream outputStream) {
        if (null == outputStream) {
            return;
        }

        try {
            outputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void close(Reader reader) {
        if (null == reader) {
            return;
        }

        try {
            reader.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void close(Writer writer) {
        if (null == writer) {
            return;
        }

        try {
            writer.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static FileInputStream fileInputStream(File file) {
        try {
            return new FileInputStream(file);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static FileInputStream fileInputStream(String filePath) {
        try {
            return new FileInputStream(filePath);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static FileOutputStream fileOutputStream(File file) {
        Assert.notNull(file, "#134 file is null, file=" + file);

        try {
            File parentFile = file.getParentFile();
            if (null != parentFile && !parentFile.exists()) {
                parentFile.mkdirs();
            }

            return new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static FileOutputStream fileOutputStream(String file) {
        try {
            File _file = new File(file);
            File _parentFile = _file.getParentFile();
            if (null != _parentFile) {
                _parentFile.mkdirs();
            } else {
                log.warn("#156 _parentFile of " + FileUtil.getCanonicalPath(_file) + " is null");
            }

            return new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static FileReader fileReader(File file) {
        try {
            return new FileReader(file);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static Reader fileReader(String fileName, String charSet) {
        try {
            FileInputStream fileInputStream = fileInputStream(fileName);
            return new InputStreamReader(fileInputStream, charSet);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] toByteArray(File file) {
        InputStream fileInputStream = fileInputStream(file);
        return toByteArray(fileInputStream);
    }

    public static byte[] toByteArray(URL url) {
        InputStream inputStream = JavaNetUrlUtil.openStream(url);
        return toByteArray(inputStream);
    }

    public static byte[] toByteArray(Object value) {
        if (null == value) {
            return null;
        }

        if (value instanceof byte[]) {
            return (byte[]) value;
        }

        if (value instanceof String) {
            return ((String) value).getBytes();
        }

        if (value instanceof InputStream) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            write((InputStream) value, byteArrayOutputStream, true, false);
            return byteArrayOutputStream.toByteArray();
        }

        if (value instanceof Reader) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            write((Reader) value, new OutputStreamWriter(out));
            return out.toByteArray();
        }

        throw new IllegalArgumentException("#204 value=" + value + ", value.type=" + value.getClass());
    }

    public static InputStream toInputStream(Object value) {
        if (null == value) {
            log.error("#252 toInputStream, value=" + value);
            return null;
        }

        if (value instanceof byte[]) {
            return new ByteArrayInputStream((byte[]) value);
        }

        if (value instanceof File) {
            return fileInputStream((File) value);
        }

        if (value instanceof Reader) {
            String body = IoUtil.toString((Reader) value);
            byte[] utf8Bytes = StringUtil.getUtf8Bytes(body);
            return new ByteArrayInputStream(utf8Bytes);
        }

        if (value instanceof String) {
            return new ByteArrayInputStream(((String) value).getBytes());
        }

        throw new RuntimeException("#121 value=" + value + ", type=" + value.getClass());
    }

    public static Reader toReader(Object value) {
        if (null == value) {
            return null;
        }

        if (value instanceof byte[]) {
            ByteArrayInputStream inputStream = new ByteArrayInputStream((byte[]) value);
            return new InputStreamReader(inputStream);
        }

        if (value instanceof InputStream) {
            return new InputStreamReader((InputStream) value);
        }

        if (value instanceof File) {
            FileInputStream fileInputStream = IoUtil.fileInputStream((File) value);
            return IoUtil.inputStreamReader(fileInputStream, "UTF-8");
        }

        throw new RuntimeException("#163 value=" + value + ", type=" + value.getClass());
    }

    public static String toString(InputStream inputStream) {
        byte[] bytes = toByteArray(inputStream);
        if (null == bytes) {
            return null;
        }

        return new String(bytes);
    }

    public static String toString(File file, String charsetName) {
        InputStream fileInputStream = fileInputStream(file);
        return toString(fileInputStream, charsetName);
    }

    public static String toString(InputStream inputStream, String charsetName) {
        if (null == inputStream || null == charsetName) {
            return null;
        }

        try {
            byte[] byteArray = toByteArray(inputStream);
            return new String(byteArray, charsetName);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static String toString(Object value) {
        if (null == value) {
            return null;
        }

        if (value instanceof String) {
            return (String) value;
        }

        if (value instanceof Boolean) {
            return value.toString();
        }

        if (value instanceof byte[]) {
            return new String((byte[]) value);
        }

        if (value instanceof File) {
            return bufferedReadLineToString((File) value);
        }

        if (value instanceof InputStream) {
            byte[] byteArray = toByteArray((InputStream) value);
            return new String(byteArray);
        }

        throw new RuntimeException("#137 value=" + value + ", type=" + value.getClass());
    }

    public static String toString(Reader reader) {
        StringWriter stringWriter = new StringWriter();
        write(reader, stringWriter);
        return stringWriter.toString();
    }

    public static void write(String in, File out) {
        write(in.getBytes(), out);
    }

    public static void write(byte[] bytes, File out) {
        FileOutputStream fileOutputStream = fileOutputStream(out);
        write(bytes, fileOutputStream);
    }

    public static void write(byte[] bytes, OutputStream outputStream) {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
        write(inputStream, outputStream);
    }

    public static void write(File from, File to) {
        InputStream fileInputStream = fileInputStream(from);
        write(fileInputStream, to);
    }

    public static void write(InputStream from, File to) {
        FileOutputStream fileOutputStream = fileOutputStream(to);
        write(from, fileOutputStream);
    }

    public static void write(InputStream inputStream, OutputStream outputStream) {
        write(inputStream, outputStream, true, false);
    }

    /**
     * @param inputStream
     * @param outputStream
     * @param closeWhenDone 完成后时候 close 掉 stream
     * @param quiet         如果传入true，遇到Broken pipe异常则会不打印堆栈
     */
    public static void write(InputStream inputStream, OutputStream outputStream, Boolean closeWhenDone, Boolean quiet) {
        if (null == inputStream || null == outputStream) {
            log.error("#339 inputStream or outputStream is null, inputStream=" + inputStream + ", outputStream=" + outputStream);
            return;
        }

        try {
            byte[] buf = new byte[1024];
            int flag = 0;
            while ((flag = inputStream.read(buf)) != -1) {
                outputStream.write(buf, 0, flag);
            }
        } catch (Throwable e) {
            if (null != quiet && quiet && ("" + e).contains("IOException: Broken pipe")) {
                log.error("#326 write error, Broken pipe, e=" + e);
            } else {
                throw new RuntimeException(e);
            }
        } finally {
            if (null != closeWhenDone && closeWhenDone) {
                close(inputStream);
                close(outputStream);
            }
        }
    }

    public static void write(String from, Writer to) {
        try {
            to.write(from);
            to.flush();
            to.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            close(to);
        }
    }

    public static void write(Reader reader, Writer writer) {
        if (null == reader || null == writer) {
            log.error("reader or writer is null, reader=" + reader + ", writer=" + writer);
            return;
        }

        try {
            char[] buf = new char[1024];
            int flag = 0;
            while ((flag = reader.read(buf)) != -1) {
                writer.write(buf, 0, flag);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            close(reader);
            close(writer);
        }
    }

    public static void write(String in, OutputStream out) {
        if (null == in || null == out) {
            log.error("#446 write error, input=" + in + ", to=" + out);
            return;
        }

        byte[] bytes = in.getBytes();
        write(bytes, out);
    }

    public static Set<String> bufferedReadLineToStringSet(Reader reader) {
        if (null == reader) {
            return null;
        }

        try {
            Set<String> lines = new HashSet<String>();
            BufferedReader bufferedReader = new BufferedReader(reader);
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                lines.add(line);
            }

            IoUtil.close(bufferedReader);
            return lines;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static List<String> bufferedReadLineToStringList(File file) {
        FileReader fileReader = IoUtil.fileReader(file);
        return bufferedReadLineToStringList(fileReader);
    }

    public static List<String> bufferedReadLineToStringList(Reader reader) {
        if (null == reader) {
            return null;
        }

        try {
            List<String> lines = new ArrayList<String>();
            BufferedReader bufferedReader = new BufferedReader(reader);
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                lines.add(line);
            }

            IoUtil.close(bufferedReader);
            return lines;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Set<String> bufferedReadLineToStringSet(InputStream inputStream) {
        if (null == inputStream) {
            return null;
        }

        InputStreamReader reader = new InputStreamReader(inputStream);
        return bufferedReadLineToStringSet(reader);
    }

    public static List<String> bufferedReadLineToStringList(InputStream inputStream) {
        if (null == inputStream) {
            return null;
        }

        InputStreamReader reader = new InputStreamReader(inputStream);
        return bufferedReadLineToStringList(reader);
    }

    public static Writer fileWriter(File file) {
        try {
            return new FileWriter(file);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void flush(OutputStream outputStream) {
        try {
            outputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Object readObject(byte[] bytes) {
        if (null == bytes) {
            return null;
        }

        try {
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            ObjectInputStream objectInputStream = new ObjectInputStream(in);
            return objectInputStream.readObject();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] writeObject(Object object) {
        if (null == object) {
            return null;
        }

        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(object);
            return byteArrayOutputStream.toByteArray();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static int available(InputStream inputStream) {
        try {
            return inputStream.available();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void skip(InputStream inputStream, long skip) {
        try {
            inputStream.skip(skip);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static InputStreamReader inputStreamReader(InputStream inputStream, String charsetName) {
        try {
            return new InputStreamReader(inputStream, charsetName);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static String readLine(Reader reader) {
        try {
            String line = "";
            String result = "";
            BufferedReader bufferedReader = new BufferedReader(reader);
            while ((line = bufferedReader.readLine()) != null) {
                result += line;
            }
            return result;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            close(reader);
        }
    }

    public static byte[] urlToByteArray(String url) {
        URL _url = JavaNetUrlUtil.newUrl(url);
        return toByteArray(_url);
    }
}
